<template>
  <!--
    spuName spu名称
    description spu描述
    tmId 品牌
    spuImageList 图片列表
    spuSaleAttrList 销售属性列表
  -->
  <el-card class="spu-card">
    <el-form ref="formRef" :model="spuForm" :rules="rules" label-width="100px">
      <el-form-item label="SPU名称" prop="spuName">
        <el-input v-model="spuForm.spuName" placeholder="请输入SPU名称" />
      </el-form-item>
      <el-form-item label="SPU描述" prop="description">
        <el-input
          v-model="spuForm.description"
          type="textarea"
          :rows="2"
          placeholder="请输入SPU描述"
        />
      </el-form-item>
      <el-form-item label="SPU品牌" prop="tmId">
        <el-select v-model="spuForm.tmId" placeholder="请选择SPU品牌">
          <el-option
            v-for="tm in trademarkList"
            :key="tm.id"
            :label="tm.tmName"
            :value="tm.id"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="图片列表" prop="spuImageList">
        <!--
          上传图片组件 el-upload
            action 上传图片服务器地址
            list-type="picture-card" 卡片风格
            on-preview 点击预览按钮触发
            on-remove 点击删除按钮触发
            file-list 上传的图片文件列表（暂时用不上）

            需要上传图片数据，需要在上传成功时，将数据给添加到数组中即可

            限制图片上传的数量
               :limit="3" 的确可以限制上传的数量
              但是上传图片按钮还在显示，导致用户以为还可以上传，不太好
              希望上传图片按钮要隐藏掉
              通过样式审查，发现：
                el-upload el-upload--picture-card调为display:none
                如何做？
                  定义一个父级类名，通过父级选择器找到el-upload--picture-card
                  再调为display:none就ok
                加上spu-upload就会隐藏，不加上就不会隐藏
        -->
        <el-upload
          :class="{
            'spu-upload': spuForm.spuImageList.length >= limit
          }"
          action="/dev-api/admin/product/fileUpload"
          list-type="picture-card"
          :on-preview="handlePictureCardPreview"
          :on-remove="handleRemove"
          :before-upload="beforeAvatarUpload"
          :on-success="handleAvatarSuccess"
          :limit="limit"
        >
          <i class="el-icon-plus" />
        </el-upload>
        <p>只能上传jpg/png文件，且不超过500kb</p>
        <!-- el-dialog 对话框：用来预览图片 -->
        <el-dialog :visible.sync="dialogVisible">
          <img width="100%" :src="dialogImageUrl" alt="" />
        </el-dialog>
      </el-form-item>
      <el-form-item label="销售属性" prop="spuSaleAttrList">
        <el-select v-model="selectedBaseSaleAttr" placeholder="请选择销售属性">
          <el-option
            v-for="base in filterSaleAttrList"
            :key="base.id"
            :label="base.name"
            :value="`${base.id}:${base.name}`"
          />
        </el-select>
        <el-button
          type="primary"
          icon="el-icon-plus"
          class="spu-btn"
          :disabled="!selectedBaseSaleAttr"
          @click="addSaleAttr"
        >
          添加销售属性
        </el-button>
        <el-table :data="spuForm.spuSaleAttrList" border class="spu-table">
          <el-table-column
            type="index"
            label="序号"
            width="50"
            align="center"
          />
          <el-table-column
            prop="saleAttrName"
            label="销售属性名称"
            width="120"
          />
          <el-table-column prop="description" label="销售属性值列表">
            <template v-slot="{ row, $index }">
              <!-- 展示销售属性值数据 -->
              <el-tag
                v-for="(saleAttrValue, index) in row.spuSaleAttrValueList"
                :key="index"
                class="spu-tag"
                closable
                @close="delSaleAttrValue(row, index)"
              >
                {{ saleAttrValue.saleAttrValueName }}
              </el-tag>
              <!--
                编辑模式和显示模式的切换：
                  编辑模式 input
                  显示模式 button

                  1. 第一次，显示显示模式 - button
                    row.isShowEdit = false
                    第一次可以不添加isShowEdit属性
                    因为不添加属性的话，读取不存在的属性值是undefined，对应布尔值也是false
                  2. 点击按钮变成编辑模式 - input
                    row.isShowEdit = true
                    为了确保数据是响应式：$set
                  3. 编辑模式输入框要聚焦
                    ref获取DOM元素，等DOM元素渲染完成（$nextTick），focus聚焦
                  4. 收集数据
                    v-model="saleAttrValueName"
                  5. 失去焦点、按enter建时，要将数据添加到属性值里面
                    @blur
                    @keyup.enter.native
               -->
              <el-input
                v-if="row.isShowEdit"
                ref="input"
                v-model="saleAttrValueName"
                placeholder="请输入属性值"
                size="small"
                class="spu-input"
                @blur="addSaleAttrValue(row)"
                @keyup.enter.native="addSaleAttrValue(row)"
              />
              <el-button
                v-else
                size="small"
                icon="el-icon-plus"
                @click="showEdit(row)"
              >
                添加属性值
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="70">
            <template v-slot="{ row, $index }">
              <el-button
                type="danger"
                size="small"
                icon="el-icon-delete"
                @click="delSaleAttr($index)"
              />
            </template>
          </el-table-column>
        </el-table>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="addSpu">确定</el-button>
        <el-button @click="cancel">取消</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>

<script>
import { mapState } from 'vuex'
import { getAllTrademarkList } from '@/api/product/trademark'
import { getBaseSaleAttrList, saveSpu } from '@/api/product/spu'

export default {
  name: 'AddOrUpdateSpu',
  data() {
    // 自定义校验规则函数
    const validator = (rule, value, callback) => {
      /*
        rule 一个对象，里面包含规则名称
        value 表单项的值
        callback 一个函数，必须调用
          callback() 代表表单校验通过
          callback(new Error('xxx')) 代表表单校验失败，参数就是失败原因（提示错误信息）
      */
      // 至少添加一个销售属性
      if (value.length === 0) {
        callback(new Error('至少添加一个销售属性'))
        return
      }
      // 并且每个销售属性至少添加一个销售属性值
      /*
        value:
        [
          {
            saleAttrName: '颜色',
            // 销售属性值
            spuSaleAttrValueList: [{ saleAttrValueName: '111' }],
            isShowEdit: false
          }
        ]
      */
      // 找是否有一个销售属性没有值
      // some可以操作次数更少，性能更好
      if (
        value.some((saleAttr) => saleAttr.spuSaleAttrValueList.length === 0)
      ) {
        callback(new Error('每个销售属性至少添加一个销售属性值'))
        return
      }
      // 找所有看是否都有至少一个值
      // if (
      //   !value.every((saleAttr) => saleAttr.spuSaleAttrValueList.length > 0)
      // ) {
      //   callback(new Error('每个销售属性至少添加一个销售属性值'))
      //   return
      // }

      // 都满足，必须调用
      // 否则校验会卡住，呈现即不成功，又不失败的效果
      callback() // 校验通过
    }

    return {
      spuForm: {
        spuName: '', // 名称
        description: '', // 描述
        tmId: '', // 品牌
        spuImageList: [], // 图片列表
        spuSaleAttrList: [] // 销售属性列表
      },
      rules: {
        spuName: [
          { required: true, message: '请输入spu名称', trigger: 'blur' }
        ],
        description: [
          { required: true, message: '请输入spu描述', trigger: 'blur' }
        ],
        tmId: [{ required: true, message: '请选择spu品牌' }],
        spuImageList: [
          {
            required: true,
            type: 'array',
            message: '请上传spu图片'
          }
        ],
        // 至少添加一个销售属性 -》 type array
        // 并且每个销售属性至少添加一个销售属性值
        spuSaleAttrList: [
          // {
          //   required: true,
          //   type: 'array', // 数组至少有一项值
          //   message: '请添加spu销售属性',
          //   trigger: 'blur'
          // }
          {
            required: true,
            validator
          }
        ]
      },
      dialogImageUrl: '',
      dialogVisible: false,
      // spuImageList: [],
      limit: 20,
      trademarkList: [],
      // spuSaleAttrList: [],
      // 基础销售属性数据（销售属性名）
      baseSaleAttrList: [],
      // 选中的销售属性数据
      selectedBaseSaleAttr: '',
      // 输入的属性值名称
      saleAttrValueName: ''
    }
  },
  computed: {
    ...mapState('category', ['category3Id']),
    filterSaleAttrList() {
      return this.baseSaleAttrList.filter((baseSaleAttr) => {
        // 判断当前销售属性名称，在不在spuSaleAttrList里面，如果在就要过滤掉
        // some方法：只要有一个满足条件返回true，所有都不满足才是false
        return !this.spuForm.spuSaleAttrList.some(
          (saleAttr) => saleAttr.saleAttrName === baseSaleAttr.name
        )
      })
    }
  },
  async mounted() {
    /*
      const trademarkListRes = await getAllTrademarkList()
      this.trademarkList = trademarkListRes.data
      const baseSaleAttrListRes = await getBaseSaleAttrList()
      this.baseSaleAttrList = baseSaleAttrListRes.data

      此时发送请求，有一个不太好的地方？
        先发送第一个请求，等第一个结束（await）才会发送第二个请求，请求时间较长
        解决：应该同时发送两个请求
           Promise.all([promise1, promise2, promise3...])
          等所有promise都成功才成功，只要有一个失败就失败
    */
    // const trademarkListRes = await getAllTrademarkList() // 10
    // this.trademarkList = trademarkListRes.data
    // const baseSaleAttrListRes = await getBaseSaleAttrList() // 10
    // this.baseSaleAttrList = baseSaleAttrListRes.data

    const [trademarkListRes, baseSaleAttrListRes] = await Promise.all([
      getAllTrademarkList(),
      getBaseSaleAttrList()
    ])

    this.trademarkList = trademarkListRes.data
    this.baseSaleAttrList = baseSaleAttrListRes.data
  },
  methods: {
    // 添加SPU
    addSpu() {
      this.$refs.formRef.validate(async (valid) => {
        if (valid) {
          /*
            {
              "category3Id": 0,
              "description": "string",
              "id": 0, // 添加不需要id，更新才需要
              "spuImageList": [
                {
                  "id": 0, // 添加不需要id
                  "imgName": "string",
                  "imgUrl": "string",
                  "spuId": 0 // 添加不需要id
                }
              ],
              "spuName": "string",
              "spuSaleAttrList": [
                {
                  "baseSaleAttrId": 0,
                  "saleAttrName": "string",
                  "spuSaleAttrValueList": [
                    {
                      "baseSaleAttrId": 0,
                      "saleAttrValueName": "string",
                    }
                  ]
                }
              ],
              "tmId": 0
            }
          */
          const { category3Id, spuForm } = this

          await saveSpu({
            category3Id,
            ...spuForm
          })

          this.$message.success('添加SPU成功')
          this.cancel()
        }
      })
    },
    // 删除销售属性值
    delSaleAttrValue(row, index) {
      row.spuSaleAttrValueList.splice(index, 1)
    },
    // 添加属性值
    addSaleAttrValue(row) {
      /*
        当前行数据：
          {
            saleAttrName: 销售属性名称
            spuSaleAttrValueList: [] 销售属性值列表
          }

        row.spuSaleAttrValueList.push()能不能使用push方法？
          当前行数据的有spuSaleAttrValueList属性，并且她为数组
          那么行数据有吗？看添加属性时，添加的对象中有没有这个数据
            并没有
          解决：所以在添加属性的时候，就应该加上这个数据，并且值为数组

          数组要push一个怎么样的数据？看接口文档对数据的要求
      */
      if (this.saleAttrValueName) {
        row.spuSaleAttrValueList.push({
          baseSaleAttrId: row.baseSaleAttrId,
          saleAttrValueName: this.saleAttrValueName
        })
      }

      // 变成显示模式
      row.isShowEdit = false
      // 清空数据
      this.saleAttrValueName = ''
    },
    // 进入编辑模式
    async showEdit(row) {
      this.$set(row, 'isShowEdit', true)
      await this.$nextTick()
      this.$refs.input.focus()
    },
    /*
      添加销售属性，在下面table显示，同时上面select需要删除这个数据
      table删除销售属性，同时上面select要出现这个数据，才能重新选择

      实现：
        1. 添加时，还要删除数据
           删除时，还需要添加数据

        2. 计算属性来实现
          定义一个filterSaleAttrList，通过baseSaleAttrList与下面spuSaleAttrList进行过滤
          如果baseSaleAttrList中的值，出现在spuSaleAttrList里面，就要过滤
          那么返回值就是没有添加上的数据，这个数据在select展示
    */
    // 删除销售属性
    delSaleAttr(index) {
      this.spuForm.spuSaleAttrList.splice(index, 1)
    },
    // 添加销售属性按钮回调
    addSaleAttr() {
      const [baseSaleAttrId, saleAttrName] =
        this.selectedBaseSaleAttr.split(':')

      this.spuForm.spuSaleAttrList.push({
        // 需要遍历查找一次
        // baseSaleAttrId: this.baseSaleAttrList.find(
        //   (base) => base.name === this.selectedBaseSaleAttr
        // ).id,
        // 不要遍历 -> select收集的值既要有name也要有id
        baseSaleAttrId: +baseSaleAttrId,
        saleAttrName,
        spuSaleAttrValueList: [] // 这个属性，为了将来添加属性值可以用push方法
      })

      this.selectedBaseSaleAttr = ''
    },
    cancel() {
      this.$emit('setIsShow', 1)
    },
    handleRemove(file) {
      // console.log(file, fileList)
      // 默认点击删除，upload组件会讲数据删除，但是组件本身数据spuImageList不会发生变化
      // 需要将图片手动给删除
      this.spuForm.spuImageList = this.spuImageList.filter((img) => {
        return img.imgUrl !== file.response.data
      })
    },
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url
      this.dialogVisible = true
    },
    // 上传之前触发的回调
    beforeAvatarUpload(file) {
      // file 当前上传的文件对象
      // file.type 文件类型 file.size 文件大小
      const isJPGOrPNG = file.type === 'image/jpeg' || file.type === 'image/png'
      const isLt500 = file.size / 1024 <= 500

      if (!isJPGOrPNG) {
        this.$message.error('上传头像图片只能是 JPG 或 PNG 格式!')
      }
      if (!isLt500) {
        this.$message.error('上传头像图片大小不能超过 500KB!')
      }
      // 函数返回值决定能不能上传图片
      return isJPGOrPNG && isLt500
    },
    // 上传成功的回调
    handleAvatarSuccess(res, file) {
      // upload没办法触发表单验证，只能自己清空结果了
      // 清空表单校验结果 0 - 1
      if (this.spuForm.spuImageList.length === 0) {
        this.$refs.formRef.clearValidate(['spuImageList'])
      }
      // 将响应的图片地址，更新到表单数据中
      this.spuForm.spuImageList.push({
        imgUrl: res.data,
        imgName: file.name
      })
    }
  }
}
</script>

<style>
.el-form-item {
  margin: 20px 0;
}
.spu-card {
  margin: 10px;
}
.spu-btn {
  margin-left: 10px;
}
.spu-upload .el-upload--picture-card {
  display: none;
}
.spu-input {
  width: 150px;
}
.spu-tag {
  margin-right: 10px;
}
</style>
